56. 绘制条形图

条形图:分类数据可视化的核心工具

条形图(Bar Plot)是展示分类数据最直观的工具。在金融分析中:

  • 比较数值:不同类别间的量值对比
  • 排名展示:Top N 股票、行业排名
  • 时间序列对比:同一指标不同时期的变化
  • 构成分析:各部分占总体的比例

条形图 vs 直方图:关键区别

特性 条形图 直方图
数据类型 分类变量 数值变量
X轴 离散类别 连续区间
条形宽度 可调整,有间隔 固定,无间隔
排列顺序 任意(通常按值排序) 固定(按区间边界)
应用场景 比较、排名 分布形态

⭐ 平台任务1:创建股指月度涨跌幅数据

Listing 1
# ⚠️ 平台原始代码 - 请原样输入至教学平台(注释除外),平台才会判定答案正确
import pandas as pd  # 导入Pandas数据分析库
import numpy as np  # 导入NumPy数值计算库

date=["2019年4月","2019年5月","2019年6月","2019年7月","2019年8月"]#创建一个日期的列表

name=['标普500指数','富时100指数','恒生指数','上证综指']#创建一个指数名称的列表

# 创建NumPy数组closeprice
closeprice=np.array([[2945.83,7418.22,29699.11,3078.34],[2752.06,7161.71,26901.09,

2898.70],[2941.76,7425.63,28542.62,2978.88],[2980.38,7586.78,27777.75,2932.51],  # 续行:closeprice=np.array(的数据项

[2926.46,7207.18,25724.73,2886.24]]) #创建收盘价的数据框

index_data = pd.DataFrame(data=closeprice,index=date,columns=name) #生成月末收盘价的数据框

index_return = index_data/index_data.shift(1)-1  #生成月度涨跌幅的数据框

index_return = index_return.dropna() #删除缺失值

print(index_return)  # 输出收益率数据
          标普500指数   富时100指数      恒生指数      上证综指
2019年5月 -0.065778 -0.034578 -0.094212 -0.058356
2019年6月  0.068930  0.036852  0.061021  0.027661
2019年7月  0.013128  0.021702 -0.026797 -0.015566
2019年8月 -0.018092 -0.050034 -0.073909 -0.015778

⭐ 平台任务2:绘制四宫格条形图

# ⚠️ 平台原始代码 - 请原样输入至教学平台(注释除外),平台才会判定答案正确
import pandas as pd  # 导入Pandas数据分析库
import numpy as np  # 导入NumPy数值计算库
from pylab import mpl  # 导入mpl模块
import matplotlib.pyplot as plt  # 导入Matplotlib绑图库
plt.rcParams['font.sans-serif']=['SimHei']  # 用黑体显示中文
plt.rcParams['axes.unicode_minus']=False   # 正常显示负号

date=["2019年4月","2019年5月","2019年6月","2019年7月","2019年8月"]#创建一个日期的列表
name=['标普500指数','富时100指数','恒生指数','上证综指']#创建一个指数名称的列表
# 创建NumPy数组closeprice
closeprice=np.array([[2945.83,7418.22,29699.11,3078.34],[2752.06,7161.71,26901.09,
2898.70],[2941.76,7425.63,28542.62,2978.88],[2980.38,7586.78,27777.75,2932.51],  # 续行:closeprice=np.array(的数据项
[2926.46,7207.18,25724.73,2886.24]]) #创建收盘价的数据框
index_data = pd.DataFrame(data=closeprice,index=date,columns=name) #生成月末收盘价的数据框
index_return = index_data/index_data.shift(1)-1  #生成月度涨跌幅的数据框
index_return = index_return.dropna() #删除缺失值
plt.figure(figsize=(11,10))  # 创建图形画布
plt.subplot(2,2,1) #第1行、第1列子图
# 绑制柱状图
plt.bar(x=index_return.columns,height=index_return.iloc[0],width=0.5,label=u"2019年5月涨跌幅",facecolor="y")
plt.xticks (fontsize=13)  # 设置X轴刻度标签
plt.yticks(fontsize=13)  # 设置Y轴刻度标签
plt.ylabel(u"涨跌幅",fontsize=13,rotation=90)  # 设置Y轴标签
plt.legend(loc=9,fontsize=13) #图列放置在中上位置
plt.grid(True)  # 显示网格线
plt.subplot(2,2,2,sharex=plt.subplot(2,2,1),sharey=plt.subplot(2,2,1)) #与第一个子图个的x轴和Y轴相同
# 绑制柱状图
plt.bar(x=index_return.columns,height=index_return.iloc[1],width=0.5,label=u"2019年6月涨跌幅",facecolor="c")
plt.xticks (fontsize=13)  # 设置X轴刻度标签
plt.yticks(fontsize=13)  # 设置Y轴刻度标签
plt.legend(loc=8,fontsize=13) #图列放置在中下位置
plt.ylabel(u"涨跌幅",fontsize=13,rotation=90)  # 设置Y轴标签
plt.grid(True)  # 显示网格线
plt.subplot(2,2,3,sharex=plt.subplot(2,2,1),sharey=plt.subplot(2,2,1))  # 选择子图位置
# 绑制柱状图
plt.bar(x=index_return.columns,height=index_return.iloc[2],width=0.5,label=u"2019年7月涨跌幅",facecolor="b")
plt.xticks (fontsize=13)  # 设置X轴刻度标签
plt.yticks(fontsize=13)  # 设置Y轴刻度标签
plt.legend(loc=9,fontsize=13)  # 添加图例
plt.ylabel(u"涨跌幅",fontsize=13,rotation=90)  # 设置Y轴标签
plt.grid(True)  # 显示网格线
plt.subplot(2,2,4,sharex=plt.subplot(2,2,1),sharey=plt.subplot(2,2,1))  # 选择子图位置
# 绑制柱状图
plt.bar(x=index_return.columns,height=index_return.iloc[3],width=0.5,label=u"2019年8月涨跌幅",facecolor="b")
plt.xticks (fontsize=13)  # 设置X轴刻度标签
plt.yticks(fontsize=13)  # 设置Y轴刻度标签
plt.legend(loc=9,fontsize=13)  # 添加图例
plt.ylabel(u"涨跌幅",fontsize=13,rotation=90)  # 设置Y轴标签
plt.grid(True)  # 显示网格线
plt.savefig("1.png")  # 保存图形至文件
Listing 2

⭐ 平台任务3:水平条形图对比6月与7月涨跌幅

# ⚠️ 平台原始代码 - 请原样输入至教学平台(注释除外),平台才会判定答案正确
import matplotlib.pyplot as plt  # 导入Matplotlib绑图库
import pandas as pd  # 导入Pandas数据分析库
import numpy as np  # 导入NumPy数值计算库
from pylab import mpl  # 导入mpl模块
 
plt.rcParams['font.sans-serif']=['SimHei']   # 用黑体显示中文
plt.rcParams['axes.unicode_minus']=False     # 正常显示负号

date=["2019年4月","2019年5月","2019年6月","2019年7月","2019年8月"]#创建一个日期的列表
name=['标普500指数','富时100指数','恒生指数','上证综指']#创建一个指数名称的列表
# 创建NumPy数组closeprice
closeprice=np.array([[2945.83,7418.22,29699.11,3078.34],[2752.06,7161.71,26901.09,
2898.70],[2941.76,7425.63,28542.62,2978.88],[2980.38,7586.78,27777.75,2932.51],  # 续行:closeprice=np.array(的数据项
[2926.46,7207.18,25724.73,2886.24]]) #创建收盘价的数据框
index_data = pd.DataFrame(data=closeprice,index=date,columns=name) #生成月末收盘价的数据框
index_return = index_data/index_data.shift(1)-1  #生成月度涨跌幅的数据框
index_return = index_return.dropna() #删除缺失值
plt.figure (figsize=(8,5))  # 创建图形画布
plt.barh(y=index_return.columns,width=index_return.iloc[1],height=0.5,label=u'2019年6月涨跌幅')  # 绑制柱状图
plt.barh(y=index_return.columns,width=index_return.iloc[2],height=0.5,label=u'2019年7月涨跌幅')  # 绑制柱状图
plt.xticks(fontsize=13)  # 设置X轴刻度标签
plt.xlabel(u"涨跌幅",fontsize=13)  # 设置X轴标签
plt.yticks (fontsize=13)  # 设置Y轴刻度标签
plt.title(u"比较4个股指在2019年6月和7月的月度涨跌幅",fontsize=13)  # 设置图表标题
plt.legend (loc=0,fontsize=13)  # 添加图例
plt.grid(True)  # 显示网格线
plt.show()  # 显示图形
plt.savefig("2.png")  # 保存图形至文件
Listing 3
(a) 基础条形图——各行业平均市盈率
(b)
<Figure size 960x480 with 0 Axes>

关键参数解析:plt.bar()plt.barh()

  • x / y:类别标签(垂直用 x,水平用 y
  • height / width:条形的长度数据
  • width / height:条形的宽度(默认 0.8)
  • color / facecolor:条形填充颜色
  • label:图例标签,配合 plt.legend() 使用

水平条形图:适合类别名称较长的场景

import matplotlib.pyplot as plt
import pandas as pd

plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False

# 创建股票数据
stocks = ['贵州茅台', '五粮液', '招商银行', '中国平安', '平安银行',
          '工商银行', '建设银行', '农业银行', '中国银行', '交通银行']
returns = [0.05, 0.03, 0.02, 0.015, 0.018,
           0.012, 0.011, 0.01, 0.009, 0.008]

df_stocks = pd.DataFrame({
    '股票': stocks,
    '收益率': returns
}).sort_values('收益率')  # 按收益率升序排序

# 绘制水平条形图
plt.figure(figsize=(10, 8))
plt.barh(df_stocks['股票'], df_stocks['收益率'],
         color=['#E3120B' if x > 0.03 else '#008080' for x in df_stocks['收益率']],
         alpha=0.8)

# 添加数值标签
for i, (idx, row) in enumerate(df_stocks.iterrows()):
    plt.text(row['收益率'] + 0.002, i, f'{row["收益率"]:.1%}',
             va='center', fontsize=10)

plt.title('股票收益率排名', fontsize=16, fontweight='bold')
plt.xlabel('收益率', fontsize=12)
plt.ylabel('股票', fontsize=12)
plt.grid(axis='x', alpha=0.3)
plt.tight_layout()
plt.show()
Figure 1: 水平条形图——股票收益率排名

水平 vs 垂直条形图:如何选择?

场景 推荐类型 理由
类别名称较长 水平 避免标签重叠
类别数量多(>10) 水平 更易阅读
时间序列 垂直 符合时间从左到右的习惯
数值比较 垂直 长度差异更直观

颜色编码技巧:用不同颜色突出重要类别(如收益率 > 3% 用红色)

分组条形图:多指标同类别对比

import matplotlib.pyplot as plt
import numpy as np

plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False

# 创建多年度数据
years = ['2021', '2022', '2023']
revenue = [100, 120, 135]      # 营业收入(亿元)
profit = [15, 18, 22]          # 净利润(亿元)
margin = [15, 15, 16.3]        # 净利率(%)

# 准备X轴位置和条形宽度
x = np.arange(len(years))
width = 0.25

fig, ax = plt.subplots(figsize=(10, 6))

# 绘制三组条形(位置依次偏移)
bars1 = ax.bar(x - width, revenue, width, label='营业收入',
               color='#2E86AB', alpha=0.8)
bars2 = ax.bar(x, profit, width, label='净利润',
               color='#E3120B', alpha=0.8)
bars3 = ax.bar(x + width, margin, width, label='净利率(%)',
               color='#008080', alpha=0.8)

# 添加数值标签
for bars in [bars1, bars2, bars3]:
    for bar in bars:
        height = bar.get_height()
        ax.text(bar.get_x() + bar.get_width()/2., height,
                f'{height:.1f}', ha='center', va='bottom', fontsize=9)

ax.set_xlabel('年份', fontsize=12)
ax.set_ylabel('金额(亿元)', fontsize=12)
ax.set_title('财务指标年度对比', fontsize=16, fontweight='bold')
ax.set_xticks(x)
ax.set_xticklabels(years)
ax.legend(fontsize=11)
ax.grid(axis='y', alpha=0.3)
plt.tight_layout()
plt.show()
Figure 2: 分组条形图——财务指标年度对比

分组条形图的核心技巧

条形位置计算公式:设 \(n\) 个年份,\(m\) 个指标,第 \(i\) 年第 \(j\) 个指标的位置:

\[x_{ij} = i + (j - \frac{m+1}{2}) \times w\]

设计要点

  • 条形宽度:通常 0.2~0.3,太细不好看、太粗易重叠
  • 间距:组间间距 ≥ 组内间距
  • 颜色:同一组用同一色系,不同组用对比色

堆叠条形图:展示整体构成

import matplotlib.pyplot as plt

plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False

# 创建收入构成数据
years = ['2021', '2022', '2023']
primary_business = [80, 90, 100]      # 主营业务收入(亿元)
other_business = [15, 20, 25]         # 其他业务收入(亿元)
investment_income = [5, 10, 10]       # 投资收益(亿元)

fig, ax = plt.subplots(figsize=(10, 6))

# 绘制三层堆叠条形(通过 bottom 参数指定起始高度)
ax.bar(years, primary_business, label='主营业务收入',
       color='#2E86AB', alpha=0.8)
ax.bar(years, other_business, bottom=primary_business,
       label='其他业务收入', color='#008080', alpha=0.8)
ax.bar(years, investment_income,
       bottom=[p+o for p, o in zip(primary_business, other_business)],
       label='投资收益', color='#F0A700', alpha=0.8)

# 添加总数值标签
for i, year in enumerate(years):
    total = primary_business[i] + other_business[i] + investment_income[i]
    ax.text(i, total + 5, f'{total}', ha='center', va='bottom',
            fontsize=11, fontweight='bold')

ax.set_xlabel('年份', fontsize=12)
ax.set_ylabel('收入(亿元)', fontsize=12)
ax.set_title('收入构成变化', fontsize=16, fontweight='bold')
ax.legend(fontsize=11, loc='upper left')
ax.grid(axis='y', alpha=0.3)
plt.tight_layout()
plt.show()
Figure 3: 堆叠条形图——收入构成分析

堆叠条形图的应用场景与注意事项

适用场景

  • 构成分析:展示整体及各部分
  • 变化追踪:观察构成的时间变化
  • 对比分析:不同类别的构成差异

注意事项

  • 不适合比较各部分的大小(基线不统一)
  • 适合展示各部分占总体的比例
  • 考虑用百分比堆叠图使比例更清晰

百分比堆叠条形图:突出比例变化

import matplotlib.pyplot as plt
import numpy as np

plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False

# 创建资产配置数据
years = ['2020', '2021', '2022', '2023', '2024']
stocks = [60, 65, 55, 50, 45]
bonds = [30, 25, 30, 35, 40]
cash = [10, 10, 15, 15, 15]

# 转换为百分比
total = np.array(stocks) + np.array(bonds) + np.array(cash)
stocks_pct = np.array(stocks) / total * 100
bonds_pct = np.array(bonds) / total * 100
cash_pct = np.array(cash) / total * 100

fig, ax = plt.subplots(figsize=(10, 6))

ax.bar(years, stocks_pct, label='股票', color='#E3120B', alpha=0.8)
ax.bar(years, bonds_pct, bottom=stocks_pct, label='债券', color='#008080', alpha=0.8)
ax.bar(years, cash_pct, bottom=stocks_pct + bonds_pct, label='现金', color='#F0A700', alpha=0.8)

# 添加百分比标签
for i, year in enumerate(years):
    ax.text(i, stocks_pct[i]/2, f'{stocks_pct[i]:.0f}%',
            ha='center', va='center', color='white', fontweight='bold')
    ax.text(i, stocks_pct[i] + bonds_pct[i]/2, f'{bonds_pct[i]:.0f}%',
            ha='center', va='center', color='white', fontweight='bold')
    ax.text(i, stocks_pct[i] + bonds_pct[i] + cash_pct[i]/2, f'{cash_pct[i]:.0f}%',
            ha='center', va='center', color='white', fontweight='bold')

ax.set_xlabel('年份', fontsize=12)
ax.set_ylabel('配置比例(%)', fontsize=12)
ax.set_title('投资组合配置变化', fontsize=16, fontweight='bold')
ax.set_ylim(0, 100)
ax.legend(fontsize=11, loc='upper right', bbox_to_anchor=(1.15, 1))
ax.grid(axis='y', alpha=0.3)
plt.tight_layout()
plt.show()
Figure 4: 百分比堆叠条形图——投资组合配置

金融应用:杜邦分析——ROE 分解

杜邦分析公式

\[\text{ROE} = \frac{\text{净利润}}{\text{销售收入}} \times \frac{\text{销售收入}}{\text{总资产}} \times \frac{\text{总资产}}{\text{股东权益}}\]

三因子含义

  • 销售净利率:盈利能力,每1元销售带来的净利润
  • 总资产周转率:效率,每1元资产创造的销售收入
  • 权益乘数:杠杆,资产与权益的比率

杜邦分析可视化:ROE 对比与因子分解

import matplotlib.pyplot as plt
import numpy as np

plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False

# 杜邦分析数据
companies = ['公司A', '公司B', '公司C', '公司D', '公司E']
net_margin = [15, 10, 8, 20, 12]
asset_turnover = [1.2, 1.5, 0.8, 1.0, 1.3]
equity_multiplier = [2.0, 1.8, 2.5, 1.5, 2.2]
roe = [nm * at * em / 100 for nm, at, em in
       zip(net_margin, asset_turnover, equity_multiplier)]

fig, axes = plt.subplots(1, 2, figsize=(15, 6))

# 左图:ROE对比
axes[0].bar(companies, roe, color='#2E86AB', alpha=0.8)
axes[0].set_title('ROE对比', fontsize=14, fontweight='bold')
axes[0].set_ylabel('ROE (%)', fontsize=12)
axes[0].set_ylim(0, max(roe) * 1.2)
axes[0].grid(axis='y', alpha=0.3)
for i, v in enumerate(roe):
    axes[0].text(i, v + 1, f'{v:.1f}%', ha='center', va='bottom')

# 右图:杜邦三因子
x = np.arange(len(companies))
width = 0.25
axes[1].bar(x - width, net_margin, width, label='销售净利率(%)',
           color='#E3120B', alpha=0.8)
axes[1].bar(x, [at * 10 for at in asset_turnover], width,
           label='总资产周转率(x10)', color='#008080', alpha=0.8)
axes[1].bar(x + width, [em * 5 for em in equity_multiplier], width,
           label='权益乘数(x5)', color='#F0A700', alpha=0.8)
axes[1].set_title('杜邦三因子', fontsize=14, fontweight='bold')
axes[1].set_xticks(x)
axes[1].set_xticklabels(companies)
axes[1].legend(fontsize=10)
axes[1].grid(axis='y', alpha=0.3)

plt.tight_layout()
plt.show()
Figure 5: 杜邦分析——ROE分解

杜邦分析的行业特点

高 ROE 可能来自不同驱动因素:

驱动因子 典型行业 特征
高利润率 白酒、医药 品牌溢价,毛利率高
高周转率 零售、快消 薄利多销,资产效率高
高杠杆 银行、地产 负债经营,放大收益

核心要点:需结合行业特点综合分析,不能仅看 ROE 数值

本章小结

  • plt.bar():绘制垂直条形图,适合时间序列和数值比较
  • plt.barh():绘制水平条形图,适合类别名称较长的排名展示
  • 分组条形图:通过调整 x 轴位置偏移实现多指标并列对比
  • 堆叠条形图:通过 bottom 参数实现各部分的层叠展示
  • 百分比堆叠图:先将数据转为百分比,再堆叠,突出比例变化